package mirrormonkey.util.annotations.parsing.memberlevel;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedList;

import junit.framework.Assert;
import mirrormonkey.util.annotations.control.AnnotationOverride;
import mirrormonkey.util.annotations.control.DefinePreset;
import mirrormonkey.util.annotations.control.IRClass;
import mirrormonkey.util.annotations.parsing.AnnotationParser;
import mirrormonkey.util.annotations.parsing.ClassIR;
import mirrormonkey.util.annotations.parsing.MethodIR;

import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Contains test cases concerning the parsing or not-parsing of methods
 * depending on if they were declared in an interface extending a given one and
 * collecting annotations from the interfaces.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class ParseByInterfaceTest {

	public static AnnotationParser parser;

	public static Collection<Class<?>> markers;

	@BeforeClass
	public static void setUpBeforeClass() {
		parser = new AnnotationParser();
		markers = new LinkedList<Class<?>>();
		markers.add(Marker.class);
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation {
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface Preset {
	}

	public static class PresetDefinition {

		@CustomAnnotation
		@DefinePreset(Preset.class)
		public Object def;

	}

	public static interface Marker {

		@CustomAnnotation
		@IRClass(value = MethodIR.class, startCollect = CustomAnnotation.class)
		public void markedMethod();

		@Preset
		@IRClass(value = MethodIR.class, startCollect = CustomAnnotation.class)
		public void presetMethod();

	}

	public static interface NoMarker {

		@CustomAnnotation
		@IRClass(value = MethodIR.class, startCollect = CustomAnnotation.class)
		public void notMarkedMethod();

	}

	public static interface BothMarkers extends Marker, NoMarker {

		@IRClass(value = MethodIR.class, startCollect = CustomAnnotation.class)
		@CustomAnnotation
		public void addedMethod();

	}

	public static class ImplementMarker implements Marker {

		@Override
		public void markedMethod() {
		}

		@Override
		public void presetMethod() {
		}

	}

	/**
	 * Tests if IRs are created and annotations are collected from methods in
	 * marked interfaces and their implementations and if presets from markers
	 * are parsed.
	 */
	@Test
	public void testCollectFromMarkerMethods() {
		ClassIR ir = parser.parseClass(ImplementMarker.class, markers);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] {
				{ CustomAnnotation.class }, { CustomAnnotation.class } });
	}

	public static class ImplementNoMarker implements NoMarker {
		@Override
		public void notMarkedMethod() {
		}
	}

	/**
	 * Tests if IRs are not created if the marker is not specified.
	 */
	@Test
	public void testRejectFromNonMarkerMethods() {
		ClassIR ir = parser.parseClass(ImplementNoMarker.class, markers);
		for (Class<?> i : new Class<?>[] { Constructor.class, Method.class,
				Field.class }) {
			Assert.assertTrue(ir.memberIRs.get(i).isEmpty());
		}
	}

	public static class ImplementBothMarkers implements BothMarkers {

		@Override
		public void markedMethod() {
		}

		@Override
		public void presetMethod() {
		}

		@Override
		public void notMarkedMethod() {
		}

		@Override
		public void addedMethod() {
		}

	}

	/**
	 * Tests if IRs are created for both the interface methods and the methods
	 * defined in the implementing classes. Also, some interface hierarchy
	 * tests.
	 */
	@Test
	public void testInterfaceHierarchy() {
		ClassIR ir = parser.parseClass(ImplementBothMarkers.class, markers);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] {
				{ CustomAnnotation.class }, { CustomAnnotation.class },
				{ CustomAnnotation.class } });
	}

	public static class MarkInImplementation implements NoMarker {

		@Override
		@CustomAnnotation
		@IRClass(value = MethodIR.class, startCollect = { CustomAnnotation.class })
		public void notMarkedMethod() {
		}

	}

	/**
	 * Tests if it is still possible to create an IR for an implemented method
	 * even if markers are defined and the method is not marked.
	 */
	@Test
	public void testClassIR() {
		ClassIR ir = parser.parseClass(MarkInImplementation.class, markers);
		PresetMemberTest.checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation.class } });
	}

	public static interface SameAsMarker {
		@CustomAnnotation
		@IRClass(startCollect = CustomAnnotation.class)
		public void presetMethod();
	}

	public static class ImplementSimilarInterfaces implements Marker,
			SameAsMarker {
		@Override
		public void markedMethod() {
		}

		@Override
		public void presetMethod() {
		}
	}

	/**
	 * Tests if a single IR is created for two methods stemming from different
	 * interface hierarchies but which share an implementation because they have
	 * the same signature.
	 */
	@Test
	public void testInterfaceSignature() {
		ClassIR ir = parser.parseClass(ImplementSimilarInterfaces.class,
				markers);
		Assert.assertEquals(2, ir.memberIRs.get(Method.class).size());
	}

	public static interface ExtendMarker extends Marker {
		@Override
		@AnnotationOverride
		public void markedMethod();
	}

	public static class ImplementExtendMarker implements ExtendMarker {

		@Override
		public void presetMethod() {
		}

		@Override
		public void markedMethod() {
		}

	}

	/**
	 * Tests if it is possible to remove IRs from an interface inherits from
	 * another.
	 */
	@Test
	public void testDeclarationAnnotationOverride() {
		ClassIR ir = parser.parseClass(ImplementExtendMarker.class, markers);
		Assert.assertEquals(1, ir.memberIRs.get(Method.class).size());
	}

	public static class ImplementMarkerAndOverride implements Marker {

		@Override
		@AnnotationOverride
		public void markedMethod() {
		}

		@Override
		public void presetMethod() {
		}

	}

	/**
	 * Tests if it possible to remove IRs that were created when parsing the
	 * interfaces from within the implementation.
	 */
	@Test
	public void testAnnotationOverride() {
		ClassIR ir = parser.parseClass(ImplementMarkerAndOverride.class,
				markers);
		Assert.assertEquals(1, ir.memberIRs.get(Method.class).size());
	}

	public static interface EmptyExtendMarker extends Marker {
	}

	public static class ImplementEmptyExtendMarker implements EmptyExtendMarker {
		@Override
		public void markedMethod() {
		}

		@Override
		public void presetMethod() {
		}
	}

	/**
	 * Tests if annotations defined in an interface that is extended by a marker
	 * interface will be ignored.
	 */
	@Test
	public void testPartialHierarchyParse() {
		Collection<Class<?>> markers = new LinkedList<Class<?>>();
		markers.add(EmptyExtendMarker.class);
		ClassIR ir = parser.parseClass(ImplementEmptyExtendMarker.class,
				markers);
		for (Class<?> i : new Class<?>[] { Constructor.class, Method.class,
				Field.class }) {
			Assert.assertTrue(ir.memberIRs.get(i).isEmpty());
		}
	}

	/**
	 * Tests if it is possible to define multiple marker interfaces.
	 */
	@Test
	public void testMultipleMarkers() {
		Collection<Class<?>> markers = new LinkedList<Class<?>>();
		markers.addAll(ParseByInterfaceTest.markers);
		markers.add(NoMarker.class);
		ClassIR ir = parser.parseClass(ImplementBothMarkers.class, markers);
		Assert.assertEquals(4, ir.memberIRs.get(Method.class).size());
	}
}
